<?php
/**
 * In this File the classes '\UF\Locale' and '\UF\LocaleHelper' are defined.
 *
 * @category   UniKap-Framework
 * @package    Core
 * @subpackage Localization
 * @author     Ulf -UniKado- Kadner <ulfikado@gmail.com>
 * @since      2014-03-29 16:17
 * @version    0.1
 */

namespace UF
{

    /**
     * A Class with some static helper methods for Locale handling
     *
     * @since  v0.1
     */
    class LocaleHelper
    {

        # <editor-fold defaultstate="collapsed" desc="- - - -   P U B L I C   S T A T I C   F I E L D S   - - - - - - - - - - - - - - -">

        # <editor-fold defaultstate="collapsed" desc="$WindowsLocales">

        /**
         * With this array you are able to convert a windows typical locale definition like
         * <code style="color: blue">German_Germany</code> to a LCID like <code style="color: blue">de_DE</code>
         *
         * But do not use it diectly (you can do it at your own risc). Use the Method ConvertWinToLCID(...)
         *
         * @var    array
         * @since  v0.1
         */
        public static $WindowsLocales = array(
            // Locale Language  => LCID
            'afrikaans' => 'af',  'albanian' => 'sq', 'amharic' => 'am', 'arabic_algeria' => 'ar_DZ',
            'arabic_bahrain' => 'ar_BH', 'arabic_egypt' => 'ar_EG', 'arabic_iraq' => 'ar_IQ',
            'arabic_jordan' => 'ar_JO', 'arabic_kuwait' => 'ar_KW', 'arabic_lebanon' => 'ar_LB',
            'arabic_libya' => 'ar_LY', 'arabic_morocco' => 'ar_MA', 'arabic_oman' => 'ar_OM', 'arabic_qatar' => 'ar_QA',
            'arabic_saudi_arabia' => 'ar_SA', 'arabic_saudiarabia' => 'ar_SA', 'arabic_syria' => 'ar_SY',
            'arabic_tunisia' => 'ar_TN', 'arabic_united_arab_emirates' => 'ar_AE', 'arabic_yemen' => 'ar_YE',
            'armenian' => 'hy', 'assamese' => 'as', 'azeri_cyrillic' => 'az_AZ', 'azeri_latin' => 'az_AZ',
            'basque' => 'eu', 'belarusian' => 'be', 'bengali_bangladesh' => 'bn', 'bengali_india' => 'bn',
            'bosnian' => 'bs', 'bulgarian' => 'bg', 'burmese' => 'my', 'catala' => 'ca', 'chinese_china' => 'zh_CN',
            'chinese_hong_kong' => 'zh_HK', 'chinese_hongkong' => 'zh_HK', 'chinese_macau_sar' => 'zh_MO',
            'chinese_macau' => 'zh_MO', 'chinese_singapore' => 'zh_SG', 'chinese_taiwan' => 'zh_TW',
            'croatian' => 'hr', 'czech' => 'cs', 'danish' => 'da', 'dutch_belgium' => 'nl_BE',
            'dutch_netherlands' => 'nl_NL', 'english_australia' => 'en_AU', 'english_belize' => 'en_BZ',
            'english_canada' => 'en_CA', 'english_caribbean' => 'en_CB', 'english_great_britain' => 'en_GB',
            'english_india' => 'en_IN', 'english_ireland' => 'en_IE', 'english_jamaica' => 'en_JM',
            'english_new_zealand' => 'en_NZ', 'english_phillippines' => 'en_PH', 'english_southern_africa' => 'en_ZA',
            'english_trinidad' => 'en_TT', 'english_united_states' => 'en_US', 'english_zimbabwe' => 'en',
            'estonian' => 'et', 'fyro_macedonia' => 'mk', 'faroese' => 'fo', 'farsi_persian' => 'fa',
            'finnish' => 'fi', 'french_belgium' => 'fr_BE', 'french_cameroon' => 'fr', 'french_canada' => 'fr_CA',
            'french_congo' => 'fr', 'french_cote_d_ivoire' => 'fr', 'french_france' => 'fr_FR',
            'french_luxembourg' => 'fr_LU', 'french_mali' => 'fr', 'french_monaco' => 'fr', 'french_morocco' => 'fr',
            'french_senegal' => 'fr', 'french_switzerland' => 'fr_CH', 'french_west_indies' => 'fr',
            'gaelic_ireland' => 'gd_IE', 'gaelic_scotland' => 'gd', 'galician' => 'gl', 'georgian' => 'ka',
            'german_austria' => 'de_AT', 'german_germany' => 'de_DE', 'german_liechtenstein' => 'de_LI',
            'german_luxembourg' => 'de_LU', 'german_switzerland' => 'de_CH', 'Greek' => 'el',
            'guarani_paraguay' => 'gn', 'gujarati' => 'gu', 'hebrew' => 'he', 'hindi' => 'hi', 'hungarian' => 'hu',
            'icelandic' => 'is', 'indonesian' => 'id', 'italian_italy' => 'it_IT', 'italian_switzerland' => 'it_CH',
            'japanese' => 'ja', 'kannada' => 'kn', 'kashmiri' => 'ks', 'kazakh' => 'kk', 'khmer' => 'km',
            'korean' => 'ko', 'lao' => 'lo', 'latin' => 'la', 'latvian' => 'lv', 'lithuanian' => 'lt',
            'malay_brunei' => 'ms_BN', 'malay_malaysia' => 'ms_MY', 'malayalam' => 'ml', 'maltese' => 'mt',
            'maori' => 'mi', 'marathi' => 'mr', 'mongolian' => 'mn', 'nepali' => 'ne', 'norwegian_bokml' => 'no_NO',
            'norwegian_nynorsk' => 'no_NO', 'oriya' => 'or', 'polish' => 'pl', 'portuguese_brazil' => 'pt_BR',
            'portuguese_portugal' => 'pt_PT', 'punjabi' => 'pa', 'raeto-romance' => 'rm', 'romanian_moldova' => 'ro_MO',
            'romanian_romania' => 'ro', 'russian' => 'ru', 'russian_moldova' => 'ru_MO', 'sanskrit' => 'sa',
            'serbian_cyrillic' => 'sr_SP', 'serbian_latin' => 'sr_SP', 'setsuana' => 'tn', 'sindhi' => 'sd',
            'slovak' => 'sk', 'slovenian' => 'sl', 'somali' => 'so', 'sorbian' => 'sb', 'spanish_argentina' => 'es_AR',
            'spanish_bolivia' => 'es_BO', 'spanish_chile' => 'es_CL', 'spanish_colombia' => 'es_CO',
            'spanish_costa_rica' => 'es_CR', 'spanish_dominican_republic' => 'es_DO', 'spanish_ecuador' => 'es_EC',
            'spanish_el_salvador' => 'es_SV', 'spanish_guatemala' => 'es_GT', 'spanish_honduras' => 'es_HN',
            'spanish_mexico' => 'es_MX', 'spanish_nicaragua' => 'es_NI', 'spanish_panama' => 'es_PA',
            'spanish_paraguay' => 'es_PY', 'spanish_peru' => 'es_PE', 'spanish_puerto_rico' => 'es_PR',
            'spanish_spain_traditional' => 'es_ES', 'spanish_spain' => 'es_ES', 'spanish_uruguay' => 'es_UY',
            'spanish_venezuela' => 'es_VE', 'swahili' => 'sw', 'swedish_finland' => 'sv_FI',
            'swedish_sweden' => 'sv_SE', 'tajik' => 'tg', 'tamil' => 'ta', 'tatar' => 'tt', 'telugu' => 'te',
            'thai' => 'th', 'tibetan' => 'bo', 'tsonga' => 'ts', 'turkish' => 'tr', 'turkmen' => 'tk',
            'ukrainian' => 'uk', 'urdu' => 'ur', 'uzbek_cyrillic' => 'uz_UZ', 'uzbek_latin' => 'uz_UZ',
            'vietnamese' => 'vi', 'welsh' => 'cy', 'xhosa' => 'xh', 'yiddish' => 'yi', 'zulu' => 'zu'
        );

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="$LanguagesAndIds">

        /**
         * Defines the english named languages as keys (in lower case) and the associated 2 char language ids as values
         * (also in lower case).
         *
         * @var    array
         * @since  v0.1
         */
        public static $LanguagesAndIds = array(
            'german' => 'de', 'french' => 'fr', 'english' => 'en', 'spanish' => 'es', 'czech' => 'cs', 'polish' => 'pl',
            'belarusian' => 'be', 'dutch' => 'nl', 'portuguese' => 'pt', 'arabic' => 'ar', 'basque' => 'eu',
            'bulgarian' => 'bg', 'catalan' => 'ca', 'chinese' => 'zh', 'croatian' => 'hr', 'danish' => 'da',
            'estonian' => 'et', 'faroese' => 'fo', 'finnish' => 'fi', 'georgian' => 'ka', 'greek' => 'el',
            'hebrew' => 'he', 'hindi' => 'hi', 'hungarian' => 'hu', 'vietnamese' => 'vi', 'ukrainian' => 'uk',
            'turkish' => 'tr', 'thai' => 'th', 'telugu' => 'te', 'tatar' => 'tt', 'tamil' => 'ta', 'syriac' => 'syr',
            'swedish' => 'sv', 'slovenian' => 'sl', 'slovak' => 'sk', 'serbian' => 'sr', 'sanskrit' => 'sa',
            'romanian' => 'ro', 'punjabi' => 'pa', 'norwegian' => 'no', 'nynorsk' => 'nn', 'mongolian' => 'mn',
            'marathi' => 'mr', 'malay' => 'ms', 'macedonian' => 'mk', 'lithuanian' => 'lt', 'latvian' => 'lv',
            'kyrgyz' => 'ky', 'korean' => 'ko', 'konkani' => 'kok', 'kazakh' => 'kk', 'kannada' => 'kn',
            'japanese' => 'ja', 'indonesian' => 'id', 'icelandic' => 'is', 'italian' => 'it', 'ita' => 'it',
            'russian' => 'ru'
        );

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="$LocalizedLanguagesAndIds">

        /**
         * Defines the localized named languages as keys and the associated 2 char language ids as values
         * (in lower case).
         *
         * @var    array
         * @since  v0.1
         */
        public static $LocalizedLanguagesAndIds = array(
            'deutsch' => 'de', 'français' => 'fr', 'english' => 'en', 'español' => 'es', 'čeština' => 'cs',
            'polski' => 'pl', 'беларускі' => 'be', 'nederlands' => 'nl', 'português' => 'pt', 'العربية' => 'ar',
            'basque' => 'eu', 'български' => 'bg', 'català' => 'ca', '中国的' => 'zh', 'hrvatski' => 'hr', 'dansk' => 'da',
            'eesti' => 'et', 'faroese' => 'fo', 'suomalainen' => 'fi', 'ქართული' => 'ka', 'ελληνικά' => 'el',
            'עִברִית' => 'he', 'हिंदी' => 'hi', 'magyar' => 'hu', 'Việt' => 'vi', 'український' => 'uk',
            'Türk' => 'tr', 'ภาษาไทย' => 'th', 'telugu' => 'te', 'татарский' => 'tt', 'தமிழ்' => 'ta', 'syriac' => 'syr',
            'svenska' => 'sv', 'slovenščina' => 'sl', 'slovenská' => 'sk', 'српски' => 'sr', 'sanskrit' => 'sa',
            'român' => 'ro', 'ਪੰਜਾਬੀ' => 'pa', 'norsk' => 'no', 'nynorsk' => 'nn', 'монгол' => 'mn',
            'मराठी' => 'mr', 'melayu' => 'ms', 'македонски' => 'mk', 'lietuvos' => 'lt', 'Latvijas' => 'lv',
            'kyrgyz' => 'ky', '한국의' => 'ko', 'konkani' => 'kok', 'kazakh' => 'kk', 'ಕನ್ನಡ' => 'kn',
            '日本の' => 'ja', 'indonesia' => 'id', 'Icelandic' => 'is', 'italiano' => 'it', 'русский' => 'ru'
        );

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="$LanguageCountries">

        /**
         * Here all 2 char language IDs are defined as the keys (lowercase). The values are numeric indicated arrays
         * containing a 2 char country ID, associated with the language (uppercase)
         *
         * @var    array
         * @since  v0.1
         */
        public static $LanguageCountries = array(
            'es' => array('ES', 'AR', 'BO', 'CL', 'CO', 'CR', 'DO', 'EC', 'SV', 'GT', 'HN', 'MX', 'NI', 'PA', 'PY', 'PE', 'PR', 'UY', 'VE'),
            'ar' => array('AE', 'BH', 'DZ', 'EG', 'IQ', 'JO', 'KW', 'LB', 'IN', 'LY', 'MA', 'OM', 'QA', 'SA', 'SY', 'TN', 'YE'),
            'en' => array('US', 'AU', 'BZ', 'CA', 'CB', 'IE', 'JM', 'NZ', 'PH', 'ZA', 'TT', 'GB', 'ZW'),
            'zh' => array('CN', 'HK', 'MO', 'CHS', 'SG', 'TW', 'CHT'),
            'de' => array('DE', 'AT', 'CH', 'IT', 'LI', 'LU', 'NA'),
            'fr' => array('FR', 'BE', 'CA', 'LU', 'MC', 'CH'),
            'cs' => array('CZ'),       'pl' => array('PL'),       'be' => array('BY'),
            'nl' => array('NL', 'BE'), 'pt' => array('PT', 'BR'),
            'eu' => array('ES'),       'bg' => array('BG'),       'ca' => array('ES'),
            'hr' => array('HR'),       'da' => array('DK'),       'et' => array('EE'),
            'fo' => array('FO'),       'fi' => array('FI'),       'ka' => array('GE'),
            'el' => array('GR'),       'he' => array('IL'),       'hi' => array('IN'),
            'hu' => array('HU'),       'vi' => array('VI'),       'uk' => array('UA'),
            'tr' => array('TR'),       'th' => array('TH'),       'te' => array('IN'),
            'tt' => array('RU'),       'ta' => array('IN'),       'syr' => array('SY'),
            'sv' => array('SE', 'FI'), 'sl' => array('SI'),       'sk' => array('SK'),
            'sr' => array('SP'),       'sa' => array('IN'),       'ro' => array('RO'),
            'pa' => array('IN'),       'no' => array('NO'),       'nn' => array('NO'),
            'mn' => array('MN'),       'mr' => array('IN'),       'ms' => array('MY', 'BN'),
            'mk' => array('MK'),       'lt' => array('LT'),       'lv' => array('LV'),
            'ky' => array('KG'),       'ko' => array('KR'),       'kok' => array('IN'),
            'kk' => array('KZ'),       'kn' => array('IN'),       'ja' => array('JP'),
            'id' => array('ID'),       'is' => array('IS'),       'it' => array('IT'),
            'ru' => array('RU')
        );

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="$Countries">

        /**
         * @var array
         * @since  v0.1
         */
        public static $Countries = array(
            'DE' => 'germany', 'FR' => 'france', 'GB' => 'england', 'ES' => 'spain', 'CZ' => 'czechia',
            'PL' => 'poland', 'BY' => 'belarusia', 'NL' => 'netherlands', 'PT' => 'portugal', 'BG' => 'bulgaria',
            'AR' => 'argentina', 'BO' => 'bolivia', 'CL' => 'chile', 'CO' => 'colombia', 'CR' => 'costa rica',
            'DO' => 'dominican republic', 'EC' => 'ecuador', 'SV' => 'el salvador', 'GT' => 'guatemala',
            'HN' => 'honduras', 'MX' => 'mexico', 'NI' => 'nicaragua', 'PA' => 'panama', 'PY' => 'paraguay',
            'PE' => 'peru', 'PR' => 'puerto rico', 'UY' => 'uruguay', 'VE' => 'venezuela',
            'AE' => 'united arab emirates', 'BH' => 'bahrain', 'DZ' => 'algeria', 'EG' => 'egypt', 'IQ' => 'iraq',
            'JO' => 'jordan', 'KW' => 'kuwait', 'LB' => 'lebanon', 'IN' => 'india', 'LY' => 'libya', 'MA' => 'morocco',
            'OM' => 'oman', 'QA' => 'qatar', 'SA' => 'saudi arabia', 'SY' => 'syria', 'TN' => 'tunisia',
            'YE' => 'yemen', 'US' => 'usa', 'AU' => 'australia', 'BZ' => 'belize', 'CA' => 'canada',
            'CB' => 'caribbean', 'IE' => 'ireland', 'JM' => 'jamaica', 'NZ' => 'new zealand', 'PH' => 'philippines',
            'ZA' => 'south africa', 'TT' => 'trinidad and tobago', 'GB' => 'united kingdom', 'ZW' => 'zimbabwe',
            'CN' => 'china', 'HK' => 'hongkong', 'MO' => 'macao', 'SG' => 'singapore', 'TW' => 'taiwan',
            'AT' => 'austria', 'CH' => 'switzerland', 'IT' => 'italy', 'LI' => 'liechtenstein', 'LU' => 'luxembourg',
            'NA' => 'namibia', 'BE' => 'belgium', 'MC' => 'monaco', 'BY' => 'belarus', 'BR' => 'brazil',
            'HR' => 'croatia', 'DK' => 'denmark', 'EE' => 'estonia', 'FO' => 'faroe islands', 'FI' => 'finland',
            'GE' => 'georgia', 'GR' => 'greece', 'IL' => 'israel', 'HU' => 'hungary', 'VI' => 'vietnam',
            'UA' => 'ukraine', 'TR' => 'turkey', 'TH' => 'thailand', 'RU' => 'russia', 'SY' => 'syria',
            'SE' => 'sweden', 'SK' => 'slovakia', 'SI' => 'slovenia', 'SP' => 'serbia', 'RO' => 'romania',
            'NO' => 'norway', 'MN' => 'mongolia', 'MY' => 'malaysia', 'BN' => 'brunei', 'MK' => 'macedonia',
            'LT' => 'lithuania', 'LV' => 'latvia', 'KG' => 'kyrgyzstan', 'KR' => 'korea', 'KZ' => 'jazakhstan',
            'JP' => 'japan', 'ID' => 'indonesia', 'IS' => 'island', 'IT' => 'italy'
        );

        # </editor-fold>

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - -   P U B L I C   S T A T I C   M E T H O D S   - - - - - - - - - - - - - -">

        /**
         * Converts a windows locale definition like <code style="color: blue">German_Germany</code> or
         * <code style="color: blue">German-Luxembourg.utf-8</code> to a LCID like <code style="color: blue">de_DE</code>
         * or <code style="color: blue">de_LU.utf-8</code>.
         *
         * @param  string $windowsLocaleDefinition THe locale definition in windows style
         * @return string Returns the LCID, or (bool)FALSE if $windowsLocaleDefinition is not a win locale definition.
         * @since  v0.1
         */
        public static function ConvertWinToLCID( $windowsLocaleDefinition )
        {
            $tmp = \explode( '.', $windowsLocaleDefinition, 2 );
            $charset = isset( $tmp[1] ) ? ( '.' . \trim( $tmp[1] ) ) : '';
            $localeDef = \rtrim( \preg_replace( '~[^a-z_]+~', '_', \strtolower( \trim( $tmp[0] ) ) ), '_' );
            if ( !isset( self::$WindowsLocales[$localeDef] ) ) return false;
            return self::$WindowsLocales[$localeDef] . $charset;
        }

        /**
         * Converts LCID like <code style="color: blue">de_DE</code> or <code style="color: blue">de_LU.utf-8</code>
         * to a windows locale definition like <code style="color: blue">German_Germany</code> or
         * <code style="color: blue">German-Luxembourg.utf-8</code>.
         *
         * @param  string $lcid The LCID
         * @return string Returns the windows locale definition, or (bool)FALSE if $lcid is not a known LCID.
         * @since  v0.1
         */
        public static function ConvertLCIDToWin( $lcid )
        {
            $tmp =  \explode( '@', $lcid, 2 );
            $tmp = \explode( '.', $tmp[0], 2 );
            $charset = isset( $tmp[1] ) ? ( '.' . \trim( $tmp[1] ) ) : '';
            $localeDef = \preg_split( '~[^a-zA-Z]+~', \trim( $tmp[0] ), 2, \PREG_SPLIT_NO_EMPTY );
            if ( 2 != \count( $localeDef ) || 2 != \strlen( $localeDef[0] ) || 2 != \strlen( $localeDef[1] ) )
                return false;
            $lcidStr = \strtolower( $localeDef[0] ) . '_' . \strtoupper( $localeDef[1] );
            if ( false === ( $winLocale = \array_search( $lcidStr, self::$WindowsLocales ) ) ) return false;
            return $winLocale . $charset;
        }

        /**
         * Converts a language definition to a ISO 2 char language ID. $language can be a english language name, or a
         * language name in the language it self (e.g.: deutsch or русский)
         *
         * @param  string $language The language definition.
         * @return string Return the language ID or (bool)FALSE if no ID is found.
         * @since  v0.1
         */
        public static function ToLID( $language )
        {
            if ( \preg_match( '~^[A-Za-z]{2}$~', $language ) )
                return \strtolower( $language );
            if ( isset( self::$LanguagesAndIds[$language] ) )
                return self::$LanguagesAndIds[$language];
            if ( isset( self::$LocalizedLanguagesAndIds[$language] ) )
                return self::$LocalizedLanguagesAndIds[$language];
            $languageLower = \strtolower( $language );
            if ( isset( self::$LanguagesAndIds[$languageLower] ) )
                return self::$LanguagesAndIds[$languageLower];
            if ( isset( self::$LocalizedLanguagesAndIds[$languageLower] ) )
                return self::$LocalizedLanguagesAndIds[$languageLower];
            return false;
        }

        /**
         * Returns the LID (2 char language ID) associated to the country with the defined CID (2 char country ID)
         *
         * @param  string $cid The 2 char country ID (e.g.: DE, FR, etc)
         * @return string Returns the 2 char language ID (LID) or (bool)FALSE if $cid is unknown.
         * @since  v0.1
         */
        public static function CIDToLID( $cid )
        {
            if ( !\preg_match( '~^[A-Za-z]{2}$~', $cid ) )
                return false;
            $cidUpper = \strtoupper( $cid );
            foreach ( self::$LanguageCountries as $lid => $data )
            {
                if ( \in_array( $cidUpper, $data ) ) return $lid;
            }
            return false;
        }

        /**
         * Expands the defined LCID (Language-Country ID) to 3 elements LID, CID and charset.
         *
         * @param  string $lcid    The LCID to expand
         * @param  string $lid     Returns the resulting LID (Language ID)
         * @param  string $cid     Returns the resulting CID (Country ID)
         * @param  string $charset Returns the resulting Charset (can be empty!)
         * @since  v0.1
         */
        public static function ExpandLCID( $lcid, &$lid, &$cid, &$charset )
        {
            $tmp = \explode( '@', $lcid, 2 ); // ignore things like @euro at the end of a LCID
            $tmp = \explode( '.', $tmp[0], 2 );
            $charset = ''; $cid = '';
            if ( 2 === \count( $tmp ) ) $charset = $tmp[1]; // charset is defined
            $result = \preg_split( '~[_-]~', $tmp[0], 2 );
            $lid = $result[0];
            if ( 2 === \count( $result ) ) $cid = $result[1];
        }

        # </editor-fold>

    }


    /**
     * Defines the Locale class as a singleton.
     *
     * The automatic initialization works like follow:
     *
     * There are 5 different ways inside the class logic, used to initialize the locale data:
     *
     * <b style="color: red">1. By URL path</b>
     *
     * If the URL starts with a path element, usable as a LCID (Language Country ID) it will be used in first case.
     * e.g. <code style="color: blue">de_DE</code> or <code style="color: blue">en-US</code> or also
     * <code style="color: blue">fr</code>.
     *
     * <b>This option is only used if {@see \UF\Locale::$InitFromUrlPath} is enabled (TRUE)</b>.
     *
     * <b style="color: red">2. By Request data</b>
     *
     * If there is a GET, POST or COOKIE parameter with the name from {@see \UF\Locale::$RequestDataParamName} is defined,
     * so it will use the parameter value in second case.
     * e.g. <code>example.com/?lang=de_DE</code> or <code>example.com/?lang=fr</code>.
     *
     * <b>This option is only used if {@see \UF\Locale::$InitFromRequestData} is enabled (TRUE)</b>.
     *
     * <b style="color: red">3. By ACCEPT_LANGUAGE Webbrowser info</b>
     *
     * If <code>$_SERVER['HTTP_ACCEPT_LANGUAGE']</code> is defined, the locale infos will be extracted from it in
     * third case.
     *
     * <b>This option is only used if {@see \UF\Locale::$InitFromAcceptLanguage} is enabled (TRUE)</b>.
     *
     * <b style="color: red">4. By a fallback locale</b>
     *
     * If {@see \UF\Locale::$FallbackLocaleString} is defined and is a valid locale definition, in fourth case a
     * Locale instance is created from it.
     *
     * <b style="color: red">5. System locale</b>
     *
     * If the options before dont give some usable locale settings, the defined system locales is used. But it must
     * not finish successfull. Maybe if locale is 'C', it fails.
     *
     * <b style="color: red">6. Internal</b>
     *
     * If all other 5 options to get locale from fails, Language 'en' is used.
     *
     * <b>Usage</b>
     *
     * Its really easy to us this class. Mainly you only have to call {@see \UF\Locale::GetInstance()} If no locale
     * is defined it will be created, by one of the options (the first with success), described before.
     *
     * If you want to configure the way to get the used locale doit like this:
     *
     * <code>
     * // Set the Locale, used as Fallback, if no other option founds a usable locale
     * \UF\Locale::$FallbackLocaleString = 'de_DE';
     *
     * // Disable the locale init from URL paths, if it is not needed. (see Field description)
     * \UF\Locale::$InitFromUrlPath = false;
     *
     * // Set Name of Request paramer that is used by this option for getting the locale.
     * \UF\Locale::$RequestDataParamName = 'ln';
     *
     * // Disable the locale init from Request data, if you dont need it (see Field description)
     * \UF\Locale::$InitFromRequestData = false;
     *
     * // Disable the locale init from $_SERVER['HTTP_ACCEPT_LANGUAGE'] if is it no need for it
     * \UF\Locale::$InitFromAcceptLanguage = false;
     * </code>
     *
     * All the configs above have to be done, if {@see \UF\Locale::GetInstance()} is called the first time by you.
     * The best way to do it is, set it from your own bootstrap project file.
     *
     * If you need the full locale string simple cast the object to a string
     * <code style="color:blue">(string) \UF\Locale::GetInstance()</code>
     *
     * @since  v0.1
     */
    final class Locale
    {

        # <editor-fold defaultstate="collapsed" desc="- - - -   P R I V A T E   F I E L D S   - - - - - - - - - - - - - - - - - - - - -">

        /**
         * The current used language (2 characters in lower case)
         *
         * @var    string
         * @since  v0.1
         */
        private $language;

        /**
         * The current used Country (2 characters in upper case)
         *
         * @var    string
         * @since  v0.1
         */
        private $country;

        /**
         * A optional charset defined by the locale.
         *
         * @var    string
         * @since  v0.1
         */
        private $charset;

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - -   P R I V A T E   S T A T I C   F I E L D S   - - - - - - - - - - - - - -">

        /**
         * Holds the singleton instance of this object..
         *
         * @var    \UF\Locale
         * @since  v0.1
         */
        private static $instance = null;

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - -   P U B L I C   S T A T I C   M E T H O D S   - - - - - - - - - - - - - -">

        /**
         * This locale is used if no other usable locale was found. You have to define it before you first access
         * {@see \UF\Locale::GetInstance()}!
         *
         * @var    string
         * @since  v0.1
         */
        public static $FallbackLocaleString = 'en.utf-8';

        /**
         * Use current URL-Path from <code>$_SERVER['SCRIPT_URL']</code> to init locale from? You have to define it
         * before you first access {@see \UF\Locale::GetInstance()}!
         *
         * If current URL-Path is defined in <code>$_SERVER['SCRIPT_URL']</code>, it will be checked, if it starts
         * with a valid LCID like <code style="color: blue">/de_AT/...</code> or <code style="color: blue">/de</code>.
         *
         * If so, the LCID will be used to init the Locale from, at first use of {@see \UF\Locale::GetInstance()}
         *
         * @var boolean
         * @since  v0.1
         */
        public static $InitFromUrlPath = true;

        /**
         * Use the here defined Name as GET, POST or COOKIE parameter name, to init the locale from? You have to define
         * it before you first access {@see \UF\Locale::GetInstance()}!
         *
         * The order is GET, POST, COOKIE.
         *
         * @var string
         * @since  v0.1
         */
        public static $RequestDataParamName = 'lang';

        /**
         * Use current GET, POST or COOKIE params to init locale from?
         *
         * The Name of the Parameter to use is defined by {@see \UF\Locale::$RequestDataParamName}.
         *
         * @var boolean
         * @since  v0.1
         */
        public static $InitFromRequestData = true;

        /**
         * Use <code>$_SERVER['HTTP_ACCEPT_LANGUAGE']</code>, if defined, to init locale from?
         *
         * @var boolean
         * @since  v0.1
         */
        public static $InitFromAcceptLanguage = true;

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - >   H I D D E N   C O N S T R U C T O R   - - - - - - - - - - - - - - - - -">

        /**
         * Uses a private constructor, because its a singleton.
         *
         * @param  string $language The language id
         * @param  string $country  The optional country id
         * @param  string $charset  The optional charset
         * @since  v0.1
         */
        private function __construct( $language, $country, $charset = null )
        {
            $this->language = $language;
            $this->country  = $country;
            $this->charset  = $charset;
            $locales = array();
            if ( \IS_WIN && ( false !== ( $lcStr = \UF\LocaleHelper::ConvertLCIDToWin( (string) $this ) ) ) )
            {
                $tmp = \explode( '_', $lcStr, 2 );
                $lid = $tmp[0]; $cid = ! empty( $tmp[1] ) ? $tmp[1] : ''; $cset = '';
                if ( ! empty( $cid ) ) {
                    $tmp = \explode( '.', $cid, 2 );
                    if ( 2 == \count( $tmp ) ) {
                        $cset .= $tmp[1]; $cid = $tmp[0];
                        $locales[] = $lid . '_' . $cid . '.' . $cset; $locales[] = $lid . '-' . $cid . '.' . $cset;
                        $locales[] = $language . '_' . $country . '.' . $charset;
                        $locales[] = $language . '-' . $country . '.' . $charset;
                        $locales[] = $lid . '_' . $cid; $locales[] = $lid . '-' . $cid;
                        $locales[] = $language . '_' . $country; $locales[] = $language . '-' . $country;
                        $locales[] = $lid; $locales[] = $language; }
                    else {
                        $locales[] = $lid . '_' . $cid; $locales[] = $lid . '-' . $cid;
                        $locales[] = $language . '_' . $country; $locales[] = $language . '-' . $country;
                        $locales[] = $lid; $locales[] = $language; } }
                else { $locales[] = $lid; }
            }
            else {
                if ( ! empty( $charset ) ) {
                    $locales[] = $language . '_' . $country . '.' . $charset;
                    $locales[] = $language . '-' . $country . '.' . $charset;
                    $locales[] = $language . '_' . $country; $locales[] = $language . '-' . $country; }
                else if ( ! empty ( $country ) ) {
                    $locales[] = $language . '_' . $country; $locales[] = $language . '-' . $country;
                    $locales[] = $language; }
                else { $locales[] = $language; } }
            \setlocale( \LC_TIME, $locales );
        }

        /**
         * Required to be private for a real singleton.
         *
         * @since  v0.1
         */
        private function __clone() { }

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - -   P U B L I C   S T A T I C   M E T H O D S   - - - - - - - - - - - - - -">

        /**
         * Returns the singleton instance of this class.
         *
         * @return \UF\Locale
         */
        public static function GetInstance()
        {
            if ( ! \is_null( self::$instance ) ) return self::$instance;
            if ( self::initByCurrentUrlPath() ) return self::$instance;
            if ( self::initByCurrentUrlParams() ) return self::$instance;
            if ( self::initByBrowserinfo() ) return self::$instance;
            if ( self::initByFallback() ) return self::$instance;
            if ( self::initBySystem() ) return self::$instance;
            self::$instance = new \UF\Locale( 'en', '' );
            return self::$instance;
        }


        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - -   P R I V A T E   S T A T I C   M E T H O D S   - - - - - - - - - - - - -">

        private static function initByBrowserinfo()
        {
            if ( ! self::$InitFromAcceptLanguage || empty( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) return false;
            // Format like: de-de,de;q=0.8,en-us;q=0.5,en;q=0.3
            $tmp = \explode( ',', $_SERVER['HTTP_ACCEPT_LANGUAGE'] );
            for ( $i = 0; $i < count( $tmp ); $i++ )
            {
                $tmp2 = \explode( ';', $tmp[$i], 2 );
                $tmp3 = \explode( '-', $tmp2[0], 2 );
                if ( \count( $tmp3 ) < 2 ) $tmp3 = \explode( '_', $tmp2[0], 2 );
                if ( \count( $tmp3 ) < 2 ) $tmp3 = array ( $tmp2[0] );
                $la   = \trim( $tmp3[0] );
                if ( \strlen( $la ) != 2)
                    continue;
                $co = null;
                $cs = null;
                if ( isset( $tmp3[1] ) )
                {
                    $tmp2 = \explode( '.', $tmp3[1], 2 );
                    $co   = \trim( $tmp2[0] );
                    if ( isset( $tmp2[1] ) ) $cs = \trim( $tmp2[1] );
                    if ( \strlen( $co ) != 2 ) $co = null;
                    else $co = \strtoupper ( $co );
                }
                self::$instance = new \UF\Locale( $la, $co, $cs );
                return true;
            }
            return false;
        }

        private static function initByCurrentUrlPath()
        {
            if ( ! self::$InitFromUrlPath || empty( $_SERVER['SCRIPT_URL'] ) )
                return false;
            $matches = null;
            if ( ! \preg_match( '~^/([a-zA-Z]{2})([_-]([a-zA-Z]{2}))?/~', $_SERVER['SCRIPT_URL'], $matches ) )
                return false;
            $lid = \strtolower( $matches[1] );
            $cid = empty( $matches[2] ) ? null : \strtoupper( $matches[3] );
            self::$instance = new \UF\Locale( $lid, $cid );
            return true;
        }

        private static function initByCurrentUrlParams()
        {
            if ( ! self::$InitFromRequestData || empty( self::$RequestDataParamName ) )
                return false;
            $value = ! empty( $_GET[self::$RequestDataParamName])
                ? $_GET[self::$RequestDataParamName]
                : ( ! empty( $_POST[self::$RequestDataParamName] )
                    ? $_POST[self::$RequestDataParamName]
                    : ( ! empty( $_COOKIE[self::$RequestDataParamName] ) ? $_COOKIE[self::$RequestDataParamName] : '' ) );
            if ( empty( $value ) ) return false;
            $matches = null;
            if ( ! \preg_match( '~^/([a-zA-Z]{2})([_-]([a-zA-Z]{2}))?/~', $value, $matches ) )
                return false;
            $lid = \strtolower( $matches[1] );
            $cid = empty( $matches[2] ) ? null : \strtoupper( $matches[3] );
            self::$instance = new \UF\Locale( $lid, $cid );
            return true;
        }

        private static function initBySystem()
        {
            $lcString = \setlocale( \LC_ALL, '0' );
            if ( empty( $lcString ) || 2 > \strlen( $lcString ) ) return false; # C ausschließen
            $lid = 0; $cid = 0; $charset = 0;
            if ( \IS_WIN )
            {   // Windows systems
                $tmp = \explode( ';', $lcString );
                foreach ( $tmp as $element )
                {
                    $tmp2 = \explode( '=', $element, 2 );
                    if ( 2 > \count( $tmp2 ) )
                    {
                        if ( false === ( $lcid = \UF\LocaleHelper::ConvertWinToLCID( $element ) ) )
                            continue;
                        \UF\LocaleHelper::ExpandLCID( $lcid, $lid, $cid, $charset );
                        self::$instance = new \UF\Locale( $lid, $cid, $charset );
                        return true;
                    }
                    if ( 'LC_CTYPE' !== \strtoupper( $tmp2[0] ) )
                        continue;
                    if ( false === ( $lcid = \UF\LocaleHelper::ConvertWinToLCID( $tmp2[1] ) ) )
                        continue;
                    \UF\LocaleHelper::ExpandLCID( $lcid, $lid, $cid, $charset );
                    self::$instance = new \UF\Locale( $lid, $cid, $charset );
                    return true;
                }
                return false;
            }
            // No windows systems
            if ( !\preg_match( '~^[a-z]{2}([_-][a-z]{2})?(@[a-z_-]+)?(\.[a-z0-9_-]{1,14})?$~i', $lcString ) )
                return false;
            \UF\LocaleHelper::ExpandLCID( $lcString, $lid, $cid, $charset );
            self::$instance = new \UF\Locale( $lid, $cid, $charset );
            return true;
        }

        private static function initByFallback()
        {
            if ( empty( self::$FallbackLocaleString ) ) return false;
            $lid = null; $cid = ''; $charset = '';
            if ( \preg_match( '~^[a-z]{2}([_-][a-z]{2})?(@[a-z_-]+)?(\.[a-z0-9_-]{1,14})?$~i',
                              self::$FallbackLocaleString ) )
            {
                \UF\LocaleHelper::ExpandLCID( self::$FallbackLocaleString, $lid, $cid, $charset );
                self::$instance = new \UF\Locale( $lid, $cid, $charset );
                return true;
            }
            if ( \IS_WIN )
            {
                if ( false === ( $lcid = \UF\LocaleHelper::ConvertWinToLCID( self::$FallbackLocaleString ) ) )
                    return false;
                \UF\LocaleHelper::ExpandLCID( $lcid, $lid, $cid, $charset );
                self::$instance = new \UF\Locale( $lid, $cid, $charset );
                return true;
            }
            return false;
        }

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - -   G E T T E R S   - - - - - - - - - - - - - - - - - - - - - - - - - - - -">

        /**
         * Returns the current defined 2 char language ID
         *
         * @return string
         * @since  v0.1
         */
        public function getLID()
        {
            return $this->language;
        }

        /**
         * Returns the current defined 2 char country ID
         *
         * @return string
         * @since  v0.1
         */
        public function getCID()
        {
            return $this->country;
        }

        /**
         * Returns the current defined charset
         *
         * @return string
         * @since  v0.1
         */
        public function getCharset()
        {
            return $this->charset;
        }

        /**
         * Overrides the magic __toString method.
         *
         * @return string
         */
        public function __toString()
        {
            return $this->language .
                ( !empty( $this->country ) ? '_' . $this->country : '' ) .
                ( !empty( $this->charset ) ? '.' . $this->charset : '' );
        }

        # </editor-fold>

    }

}

